// ------------------------------------------------------------------------------
// 
// Copyright (c) 2006-2008 Swampware, Inc.
// 
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
// 
// ------------------------------------------------------------------------------
using System;

namespace Water
{
	/// <summary>
	/// Summary description for Evaluator.
	/// </summary>
	public class Evaluator
	{

		public Evaluator()
		{
		}

		/// <summary>
		/// Evaluates any type of object.
		/// </summary>
		/// <param name="o"></param>
		/// <returns></returns>
		public virtual object Evaluate(object o, Water.Environment environment)
		{
			if (o is Water.Quote)
			{
				Water.Quote quote = (Water.Quote)o;
				return EvaluateQuote(quote, environment);
			}
			else if (o is Water.Comma)
			{
				Water.Comma comma = (Water.Comma)o;
				return EvaluateComma(comma, environment);
			}
			else if (o is Water.List)
			{
				Water.List list = (Water.List)o;
				return EvaluateList(list, environment);
			}
			else if(o is Water.Identifier)
			{
				Water.Identifier identifier = (Water.Identifier)o;
				return EvaluateIdentifier(identifier, environment);
			}
			else if(o is string)
			{
				string s = (string)o;
				return EvaluateString(s, environment);
			}
			else
			{
				return o;
			}
		}

		private object EvaluateQuote(Water.Quote quote, Water.Environment environment)
		{
			return CheckQuote(quote.Expression, environment);
		}

		private object CheckQuote(object o, Water.Environment environment)
		{
			if (o is Water.List)
			{
				Water.List list = (Water.List)o;

				Water.List results = new Water.List();
				foreach (object item in list)
				{
					results.Add(CheckQuote(item, environment));
				}
				return results;
			}
			else if (o is Water.Dictionary)
			{
				Water.Dictionary dictionary = (Water.Dictionary)o;

				Water.Dictionary results = new Water.Dictionary();
				foreach (string key in dictionary)
				{
					results.Add(key, CheckQuote(dictionary[key], environment));
				}
				return results;
			}
			else if (o is Water.Comma)
			{
				Water.Comma comma = (Water.Comma)o;
				return EvaluateComma(comma, environment);
			}
			else if (o is string)
			{
				string s = (string)o;
				return EvaluateString(s, environment);
			}
			else
			{
				return o;
			}
		}

		private object EvaluateComma(Water.Comma comma, Water.Environment environment)
		{
			return Evaluate(comma.Expression, environment);
		}

		public virtual object EvaluateList(Water.List list, Water.Environment environment)
		{
			object command = list.First();
			Water.List args = list.NotFirst();

			if (command is Water.Comma)
			{
				Water.Comma comma = (Water.Comma)command;
				object command2 = EvaluateComma(comma, environment);
				return Apply(command2, args, environment);
			}
			else if (command is Water.List)
			{
				Water.List list2 = (Water.List)command;
				object command2 = EvaluateList(list2, environment);
				return Apply(command2, args, environment);
			}
			else if (command is Water.Identifier)
			{
				Water.Identifier identifier = (Water.Identifier)command;
				object command2 = EvaluateIdentifier(identifier, environment);
				if(command2 == null)
				{
					//TODO use a Water error, you don't need a stack trace here.
					throw new System.Exception(identifier.Value + " is not defined.");
				}
				return Apply(command2, args, environment);
			}
			else
			{
				return Apply(command, args, environment);
			}
		}

		public object Apply(object command, Water.List args, Water.Environment environment)
		{
			if (command is Water.Operator)
			{
				Water.Operator op = (Water.Operator)command;
				return op.Evaluate(args, environment, this);
			}
			else if (args.Count == 0)
			{
				return Evaluate(command, environment);
			}
			else
			{
				throw new System.Exception("The operator \"" + command.ToString() + "\" is not defined.");
			}
		}

		public virtual object EvaluateIdentifier(Water.Identifier identifier, Water.Environment environment)
		{
			return environment.Identify(identifier.Value);
		}

		public virtual string EvaluateString(string s, Water.Environment environment)
		{
			char[] ach = s.ToCharArray();

			System.Text.StringBuilder tagStringBuilder = new System.Text.StringBuilder();
			System.Text.StringBuilder stringBuilder = new System.Text.StringBuilder();

			bool inTag = false;
			bool isEscaped = false;
			for(int i = 0; i < ach.Length; i++)
			{
				if(isEscaped)
				{
					if(ach[i] == '}')
					{
						isEscaped = false;
					}
					tagStringBuilder.Append(ach[i]);
				}
				else if(inTag)
				{
					if(ach[i] == '{')
					{
						isEscaped = true;
						tagStringBuilder.Append(ach[i]);
					}
					else if(ach[i] == '}')
					{
						inTag = false;

						//Replace tag
						string name = tagStringBuilder.ToString();

						object val = Evaluate(Water.Parser.QuickParse(name), environment);

						if(val == null)
						{
							throw new System.Exception("Tag evaluated to null: " + name);
						}
						string value = val.ToString();
						stringBuilder.Append(value);
						tagStringBuilder = new System.Text.StringBuilder();
					}
					else
					{
						tagStringBuilder.Append(ach[i]);
					}
				}
				else
				{
					if(ach[i] == '$' && (i + 1) < ach.Length && ach[i + 1] == '{')
					{
						inTag = true;
						i++;
					}
					else
					{
						stringBuilder.Append(ach[i]);
					}
				}
			}
			return stringBuilder.ToString();
		}

	}
}
